﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Web;
using HgCo.WindowsLive.SkyDrive.Support;
using HtmlAgilityPack;
using System.Globalization;

namespace HgCo.WindowsLive.SkyDrive
{
    // TODO: Try to send back feedbacks of operation success/failure
    // TODO: Custom exceptions
    // TODO: Thread safety!

    /// <summary>
    /// Provides methods (API) for interacting with SkyDrive online storage.
    /// </summary>
    public class SkyDriveWebClient
    {
        #region Constants and Fields

        /// <summary>
        /// The regular expression to parse user name.
        /// </summary>
        private readonly Regex RegexUserName = new Regex("(?<Id>[^@]+)@(?<Domain>.+)");

        /// <summary>
        /// The regular expression to parse the postback URL from JavaScript to log on.
        /// </summary>
        private readonly Regex RegexLogOnUploadUrl = new Regex("var\\s+srf_uPost\\s*=\\s*'(?<URL>[^']+)';");

        /// <summary>
        /// The regular expression to parse webfolderitem's path URL from HTML.
        /// </summary>
        private readonly Regex RegexWebFolderItemPathUrl = new Regex("(?i:https?://[^/]+/\\w+.aspx(?<Path>[^?]+))");

        /// <summary>
        /// The regular expression to parse webfolderitem's title attribute from HTML.
        /// </summary>
        private readonly Regex RegexWebFolderItemTitle = new Regex("(?i:(?<Name>[^\n]+)(\nShared with:\\s*(?<SharedWith>[^\n]+))?(\nDate modified:\\s*(?<DateModified>[^\n]+))?(\nType:\\s*(?<Type>[^\n]+))?(\nSize:\\s*(?<Size>[^\n]+))?(\n(?<Description>.*))?)");

        /// <summary>
        /// The regular expression to parse the webfile's download URL from JavaScript.
        /// </summary>
        private readonly Regex RegexWebFileDownloadUrl = new Regex("var\\s+_download\\s*=\\s*'(?<URL>[^']+)';");

        /// <summary>
        /// The URI of the log on page.
        /// </summary>
        private static readonly Uri LogOnUri = new Uri("http://login.live.com/login.srf?wa=wsignin1.0&rpsnv=10&ct=1242028992&rver=5.5.4177.0&wp=MBI&wreply=http:%2F%2Fskydrive.live.com%2Fwelcome.aspx%3Fmkt%3Den-us&lc=1033&id=250206&mkt=en-US");
        
        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the session used for SkyDrive communication.
        /// </summary>
        /// <value>The session.</value>
        public WebSession Session { get; private set; }
        
        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SkyDriveWebClient"/> class.
        /// </summary>
        public SkyDriveWebClient() : this(null) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="SkyDriveWebClient"/> class.
        /// </summary>
        /// <param name="session">The session to use for SkyDrive communication.</param>
        public SkyDriveWebClient(WebSession session)
        {
            Session = session != null ? session : new WebSession();
            
            // Remove form element from ElementsFalgs dictionary in order to:
            // - do not allow overlapping forms, and
            // - let form have child nodes!!
            if (HtmlNode.ElementsFlags.ContainsKey("form"))
                HtmlNode.ElementsFlags.Remove("form");
        }

        #endregion

        #region Public Methods

        #region Session Related Methods

        /// <summary>
        /// Logs on to a specified user account.
        /// </summary>
        /// <param name="userName">The name of the user.</param>
        /// <param name="userPassword">The user password.</param>
        public void LogOn(string userName, string userPassword)
        {
            WebBrowser webBrowser = new WebBrowser(Session);
            Match matchUserName = RegexUserName.Match(userName);
            string responseString = null;
            HtmlDocument responseDocument = new HtmlDocument();
            NameValueCollection parameters = new NameValueCollection();
            Uri uriRefresh = null;

            // Request-Response No. 1
            responseString = webBrowser.DownloadString(LogOnUri);

            NameValueCollection tagAttributesPPFT = HtmlDocumentHelper.GetTagAttributes(
                HtmlDocumentHelper.GetTagByName(responseString, "PPFT"));
            
            Session.AddCookie(
                "CkTst",
                String.Format(CultureInfo.InvariantCulture, "G{0}", UnixDateTimeHelper.Parse(DateTime.Now)),
                "login.live.com");
            Session.AddCookie(
                "wlidperf",
                String.Format(CultureInfo.InvariantCulture, "throughput=7&latency=421&FR=L&ST={0}", UnixDateTimeHelper.Parse(DateTime.Now)),
                ".live.com");
            Session.AddCookie(
                "WLOpt",
                String.Format(CultureInfo.InvariantCulture, "RDCache=@{0}:4&nrme=1", matchUserName.Groups["Domain"].Value),
                "login.live.com");

            parameters["idsbho"] = "1";
            parameters["PwdPad"] = "IfYouAreReadingThisYouHaveTooMu";
            parameters["LoginOptions"] = "3";
            parameters["CS"] = "";
            parameters["FedState"] = "";
            parameters["PPSX"] = "Pas";
            parameters["type"] = "11";
            parameters["login"] = userName;
            parameters["passwd"] = userPassword;
            parameters["NewUser"] = "1";
            parameters["PPFT"] = tagAttributesPPFT["value"];
            parameters["i1"] = "0";
            parameters["i2"] = "0";

            Match matchLoginUploadUrl = RegexLogOnUploadUrl.Match(responseString);
            Uri uriLoginUpload = UriHelper.GetUri(HtmlDocumentHelper.DecodeJavascriptString(
                matchLoginUploadUrl.Groups["URL"].Value));

            // Request-Response No. 2
            responseString = webBrowser.UploadValuesUrlEncoded(uriLoginUpload, parameters);

            uriRefresh = HtmlDocumentHelper.GetMetaTagRefreshUri(responseString);

            // Request-Response No. 3
            responseString = webBrowser.DownloadString(uriRefresh);

            responseDocument.LoadHtml(responseString);
            HtmlNode nodeForm = responseDocument.GetElementbyId("fmHF");
            Uri uriFormAction = UriHelper.GetUri(nodeForm.Attributes["action"].Value);
            parameters = ParseFormPostBackParameters(responseDocument, nodeForm.Attributes["name"].Value); 
            
            // Request-Response No. 4
            responseString = webBrowser.UploadValuesUrlEncoded(uriFormAction, parameters);
        }
        
        #endregion

        #region WebFolderItem Related Methods

        /// <summary>
        /// Lists webfolderitems located in SkyDrive's root.
        /// </summary>
        /// <returns>The list of webfolderitems in SkyDrive's root.</returns>
        public WebFolderItemInfo[] ListRootWebFolderItems()
        {
            WebBrowser webBrowser = new WebBrowser(Session);
            List<WebFolderItemInfo> lWebFolderItem = new List<WebFolderItemInfo>();
            Uri uriRootWebFolderBrowse = GetRootWebFolderBrowseUri(Session);

            string responseString = webBrowser.DownloadString(uriRootWebFolderBrowse);
            HtmlDocument responseDocument = new HtmlDocument();
            responseDocument.LoadHtml(responseString);

            HtmlNodeCollection nodeItemGroups = responseDocument.DocumentNode
                .SelectNodes("//div[contains(@class, 'tvContainer')]");
            foreach (HtmlNode nodeItemGroup in nodeItemGroups)
            {
                HtmlNodeCollection nodeItems = nodeItemGroup
                    .SelectNodes(".//div[contains(@class, 'tvItemContainer')]//a[@class='tvLink']");
                if (nodeItems != null)
                    foreach (HtmlNode nodeItem in nodeItems)
                    {
                        Match matchWebFolderItemTitle = RegexWebFolderItemTitle.Match(
                            HtmlDocumentHelper.DecodeUnicodeString(nodeItem.Attributes["title"].Value));
                        Match matchWebFolderItemPathUrl = RegexWebFolderItemPathUrl.Match(
                            nodeItem.Attributes["href"].Value);

                        WebFolderInfo webFolder = new WebFolderInfo
                        {
                            Name = HtmlDocumentHelper.DecodeUnicodeString(nodeItem.InnerText),
                            Description = matchWebFolderItemTitle.Groups["Description"].Value,
                            PathUrl = HttpUtility.UrlDecode(matchWebFolderItemPathUrl.Groups["Path"].Value),
                        };
                        webFolder.ContentType = WebFolderContentTypeHelper.ParseContentType(nodeItemGroup.Id);
                        if (!String.IsNullOrEmpty(matchWebFolderItemTitle.Groups["DateModified"].Value))
                            webFolder.DateModified = DateTime.Parse(
                                HtmlDocumentHelper.DecodeUnicodeString(matchWebFolderItemTitle.Groups["DateModified"].Value), 
                                CultureInfo.InvariantCulture);
                        if (!String.IsNullOrEmpty(matchWebFolderItemTitle.Groups["SharedWith"].Value))
                            webFolder.ShareType = WebFolderItemShareTypeHelper.ParseShareType(matchWebFolderItemTitle.Groups["SharedWith"].Value);
                        else webFolder.ShareType = WebFolderItemShareType.Private;

                        lWebFolderItem.Add(webFolder);
                    }
            }

            return lWebFolderItem.ToArray();
        }

        /// <summary>
        /// Lists webfolderitems located in a sub webfolder.
        /// </summary>
        /// <param name="webFolderParent">The webfolder which webfolderitems are to be listed.</param>
        /// <returns>The list of webfolderitems in the sub webfolder.</returns>
        public WebFolderItemInfo[] ListSubWebFolderItems(WebFolderInfo webFolderParent)
        {
            if (webFolderParent == null)
                throw new ArgumentNullException("webFolderParent");

            WebBrowser webBrowser = new WebBrowser(Session);
            List<WebFolderItemInfo> lWebFolderItem = new List<WebFolderItemInfo>();
            Uri uriSubWebFolderBrowse = GetSubWebFolderBrowseUri(Session, webFolderParent);

            string responseString = webBrowser.DownloadString(uriSubWebFolderBrowse);
            HtmlDocument responseDocument = new HtmlDocument();
            responseDocument.LoadHtml(responseString);

            HtmlNode nodeViewTypeOriginal = responseDocument.GetElementbyId("browseViewMenu");
            WebFolderViewType viewTypeOriginal =
                WebFolderViewTypeHelper.ParseViewType(nodeViewTypeOriginal.InnerText);

            if (viewTypeOriginal != WebFolderViewType.Details)
            {
                NameValueCollection parameters = ParseFormPostBackParameters(responseDocument, "aspnetForm");
                parameters["postVerb"] = "Update";
                parameters["postVerbData"] = String.Format(CultureInfo.InvariantCulture, "{0}:Alpha", WebFolderViewType.Details);
                webBrowser.UploadValuesUrlEncoded(uriSubWebFolderBrowse, parameters, false);

                responseString = webBrowser.DownloadString(uriSubWebFolderBrowse);
                responseDocument.LoadHtml(responseString);
            }

            HtmlNodeCollection nodeItems = responseDocument.GetElementbyId("detailsView")
                 .SelectNodes("tbody/tr[contains(@class, 'gvTableRow')]");
            if (nodeItems != null)
                foreach (HtmlNode nodeItem in nodeItems)
                {
                    WebFolderItemInfo webFolderItem = null;
                    switch (webFolderParent != null ? webFolderParent.ContentType : WebFolderContentType.Documents)
                    {
                        case WebFolderContentType.Documents:
                        case WebFolderContentType.Photos:
                            {
                                HtmlNode nodeItemName = nodeItem.SelectSingleNode("./td[1]//span[@class='gvCellText']");
                                HtmlNode nodeItemLink = nodeItem.SelectSingleNode("./td[1]//a");
                                HtmlNode nodeItemLastModified = nodeItem.SelectSingleNode("./td[2]//span[@class='gvCellText']");
                                HtmlNode nodeItemContentType = nodeItem.SelectSingleNode("./td[3]//span[@class='gvCellText']");
                                HtmlNode nodeItemSize = nodeItem.SelectSingleNode("./td[4]//span[@class='gvCellText']");
                                Match matchWebFolderItemPathUrl = RegexWebFolderItemPathUrl.Match(
                                    HtmlDocumentHelper.DecodeUnicodeString(nodeItemLink.Attributes["href"].Value));

                                string itemContentType = HtmlDocumentHelper.DecodeUnicodeString(nodeItemContentType.InnerText);
                                WebFolderItemType itemType = itemContentType.Equals("folder", StringComparison.InvariantCultureIgnoreCase) ?
                                    WebFolderItemType.Folder : WebFolderItemType.File;

                                switch (itemType)
                                {
                                    case WebFolderItemType.Folder:
                                        webFolderItem = new WebFolderInfo
                                        {
                                            Name = HtmlDocumentHelper.DecodeUnicodeString(nodeItemName.InnerText),
                                            ShareType = webFolderParent != null ? webFolderParent.ShareType : WebFolderItemShareType.Private,
                                            DateModified = DateTime.Parse(
                                                HtmlDocumentHelper.DecodeUnicodeString(nodeItemLastModified.InnerText), 
                                                CultureInfo.InvariantCulture),
                                            PathUrl = HttpUtility.UrlDecode(matchWebFolderItemPathUrl.Groups["Path"].Value),

                                            ContentType = webFolderParent != null ? webFolderParent.ContentType : WebFolderContentType.Documents
                                        };
                                        break;
                                    case WebFolderItemType.File:
                                        string pathUrl = HttpUtility.UrlDecode(matchWebFolderItemPathUrl.Groups["Path"].Value);
                                        if (".url".Equals(Path.GetExtension(pathUrl), StringComparison.InvariantCultureIgnoreCase))
                                            webFolderItem = new WebFavoriteInfo
                                            {
                                                Name = HtmlDocumentHelper.DecodeUnicodeString(nodeItemName.InnerText),
                                                ShareType = webFolderParent != null ? webFolderParent.ShareType : WebFolderItemShareType.Private,
                                                DateModified = DateTime.Parse(
                                                    HtmlDocumentHelper.DecodeUnicodeString(nodeItemLastModified.InnerText),
                                                    CultureInfo.InvariantCulture),
                                                PathUrl = pathUrl,

                                                ContentType = HtmlDocumentHelper.DecodeUnicodeString(nodeItemContentType.InnerText),
                                                Size = HttpUtility.HtmlDecode(nodeItemSize.InnerText).Trim(),
                                            };
                                        else
                                            webFolderItem = new WebFileInfo
                                            {
                                                Name = HtmlDocumentHelper.DecodeUnicodeString(nodeItemName.InnerText),
                                                ShareType = webFolderParent != null ? webFolderParent.ShareType : WebFolderItemShareType.Private,
                                                DateModified = DateTime.Parse(
                                                    HtmlDocumentHelper.DecodeUnicodeString(nodeItemLastModified.InnerText),
                                                    CultureInfo.InvariantCulture),
                                                PathUrl = pathUrl,

                                                ContentType = HtmlDocumentHelper.DecodeUnicodeString(nodeItemContentType.InnerText),
                                                Size = HttpUtility.HtmlDecode(nodeItemSize.InnerText).Trim(),
                                            };
                                        break;
                                    default:
                                        throw new NotSupportedException(itemType.ToString());
                                }
                            }
                            break;
                        case WebFolderContentType.Favorites:
                            {
                                HtmlNode nodeItemName = nodeItem.SelectSingleNode("./td[1]//span[@class='gvCellText']");
                                HtmlNode nodeItemLink = nodeItem.SelectSingleNode("./td[1]//a");
                                HtmlNode nodeItemWebAddress = nodeItem.SelectSingleNode("./td[2]//span[@class='gvCellText']");
                                Match matchWebFolderItemPathUrl = RegexWebFolderItemPathUrl.Match(
                                    HtmlDocumentHelper.DecodeUnicodeString(nodeItemLink.Attributes["href"].Value));

                                WebFolderItemType itemType = nodeItemLink.Attributes["href"].Value
                                    .Contains("browse.aspx") ? WebFolderItemType.Folder : WebFolderItemType.File;

                                switch (itemType)
                                {
                                    case WebFolderItemType.Folder:
                                        webFolderItem = new WebFolderInfo
                                        {
                                            Name = HtmlDocumentHelper.DecodeUnicodeString(nodeItemName.InnerText),
                                            ShareType = webFolderParent != null ? webFolderParent.ShareType : WebFolderItemShareType.Private,
                                            PathUrl = HttpUtility.UrlDecode(matchWebFolderItemPathUrl.Groups["Path"].Value),

                                            ContentType = webFolderParent != null ? webFolderParent.ContentType : WebFolderContentType.Documents
                                        };
                                        break;
                                    case WebFolderItemType.File:
                                        string urlWebAddress = HttpUtility.HtmlDecode(nodeItemWebAddress.InnerText).Trim();
                                        if (urlWebAddress.Length > 0)
                                            webFolderItem = new WebFavoriteInfo
                                            {
                                                Name = HtmlDocumentHelper.DecodeUnicodeString(nodeItemName.InnerText),
                                                ShareType = webFolderParent != null ? webFolderParent.ShareType : WebFolderItemShareType.Private,
                                                PathUrl = HttpUtility.UrlDecode(matchWebFolderItemPathUrl.Groups["Path"].Value),

                                                WebAddress = UriHelper.GetUri(urlWebAddress)
                                            };
                                        else
                                            webFolderItem = new WebFileInfo
                                            {
                                                Name = HtmlDocumentHelper.DecodeUnicodeString(nodeItemName.InnerText),
                                                ShareType = webFolderParent != null ? webFolderParent.ShareType : WebFolderItemShareType.Private,
                                                PathUrl = HttpUtility.UrlDecode(matchWebFolderItemPathUrl.Groups["Path"].Value)
                                            };
                                        break;
                                    default:
                                        throw new NotSupportedException(itemType.ToString());
                                }
                            }
                            break;
                    }

                    if (webFolderItem != null)
                        lWebFolderItem.Add(webFolderItem);
                }

            if (viewTypeOriginal != WebFolderViewType.Details)
            {
                NameValueCollection parameters = ParseFormPostBackParameters(responseDocument, "aspnetForm");
                parameters["postVerb"] = "Update";
                parameters["postVerbData"] = String.Format(CultureInfo.InvariantCulture, "{0}:Alpha", viewTypeOriginal);
                webBrowser.UploadValuesUrlEncoded(uriSubWebFolderBrowse, parameters, false);
            }

            return lWebFolderItem.ToArray();
        }

        /// <summary>
        /// Gets a webfolderitem with all data.
        /// </summary>
        /// <param name="webFolderItem">The webfolderitem.</param>
        /// <returns>The webfolderitem with all data.</returns>
        public WebFolderItemInfo GetWebFolderItem(WebFolderItemInfo webFolderItem)
        {
            WebBrowser webBrowser = new WebBrowser(Session);
            Uri uriWebFolderItemView = GetWebFolderItemViewUri(Session, webFolderItem);
            string responseString = webBrowser.DownloadString(uriWebFolderItemView);

            HtmlDocument responseDocument = new HtmlDocument();
            responseDocument.LoadHtml(responseString);

            HtmlNode nodeInformation = responseDocument.GetElementbyId("spProperties")
                .SelectSingleNode("./table");
            HtmlNodeCollection nodeProperties = nodeInformation.SelectNodes("./tr");

            foreach (HtmlNode nodeProperty in nodeProperties)
            {
                HtmlNode nodePropertyName = nodeProperty
                    .SelectSingleNode("./td[@class='spLabel']//div[@class='spLabelDiv']");
                HtmlNode nodePropertyValue = nodeProperty
                    .SelectSingleNode("./td[@class='spValue']//span");

                switch (HtmlDocumentHelper.DecodeUnicodeString(nodePropertyName.InnerText.ToLower(CultureInfo.InvariantCulture)))
                {
                    case "added by:":
                        HtmlNode nodeCreator = nodePropertyValue.SelectSingleNode(".//a");
                        webFolderItem.CreatorName = HtmlDocumentHelper.DecodeUnicodeString(nodeCreator.InnerText);
                        webFolderItem.CreatorUri = UriHelper.GetUri(nodeCreator.Attributes["href"].Value);
                        break;
                    case "shared with:":
                        webFolderItem.ShareType = WebFolderItemShareTypeHelper.ParseShareType(
                            HtmlDocumentHelper.DecodeUnicodeString(nodePropertyValue.InnerText));
                        break;
                    case "type:":
                        switch (webFolderItem.ItemType)
                        {
                            case WebFolderItemType.File:
                                ((WebFileInfo)webFolderItem).ContentType = HtmlDocumentHelper.DecodeUnicodeString(nodePropertyValue.InnerText);
                                break;
                            case WebFolderItemType.Folder:
                                ((WebFolderInfo)webFolderItem).ContentType = WebFolderContentTypeHelper.ParseContentType(
                                    HtmlDocumentHelper.DecodeUnicodeString(nodePropertyValue.InnerText));
                                break;
                        }
                        break;
                    case "size:":
                        webFolderItem.Size = HtmlDocumentHelper.DecodeUnicodeString(nodePropertyValue.InnerText);
                        break;
                    case "date added:":
                        webFolderItem.DateAdded = DateTime.Parse(
                            HtmlDocumentHelper.DecodeUnicodeString(nodePropertyValue.InnerText),
                            CultureInfo.InvariantCulture);
                        break;
                    case "date modified:":
                        webFolderItem.DateModified = DateTime.Parse(
                            HtmlDocumentHelper.DecodeUnicodeString(nodePropertyValue.InnerText),
                            CultureInfo.InvariantCulture);
                        break;
                }
            }

            WebFavoriteInfo webFavorite = webFolderItem as WebFavoriteInfo;
            if (webFavorite != null)
            {
                HtmlNode nodeWebAddress = responseDocument.DocumentNode
                    .SelectSingleNode("//div[@class='spFavoriteUrl']//a");
                if (nodeWebAddress != null)
                    webFavorite.WebAddress = UriHelper.GetUri(
                        nodeWebAddress.Attributes["href"].Value);
            }

            return webFolderItem;
        }

        /// <summary>
        /// Renames a webfolderitem.
        /// </summary>
        /// <param name="webFolderItem">The webfolderitem to be renamed.</param>
        /// <param name="newName">The new name.</param>
        public void RenameWebFolderItem(WebFolderItemInfo webFolderItem, string newName)
        {
            WebBrowser webBrowser = new WebBrowser(Session);
            Uri uriWebFolderItemRename = GetWebFolderItemRenameUri(Session, webFolderItem);
            string responseString = webBrowser.DownloadString(uriWebFolderItemRename);

            NameValueCollection parameters = ParseFormPostBackParameters(responseString, "aspnetForm");
            parameters["itemName"] = newName;

            webBrowser.UploadValuesUrlEncoded(uriWebFolderItemRename, parameters, false);
        }

        /// <summary>
        /// Changes the description of a webfolderitem.
        /// </summary>
        /// <param name="webFolderItem">The webfolderitem to be changed.</param>
        /// <param name="newDescription">The new description.</param>
        public void ChangeWebFolderItemDescription(WebFolderItemInfo webFolderItem, string newDescription)
        {
            WebBrowser webBrowser = new WebBrowser(Session);
            Uri uriWebFolderItemView = GetWebFolderItemViewUri(Session, webFolderItem);
            string responseString = webBrowser.DownloadString(uriWebFolderItemView);

            NameValueCollection parameters = ParseFormPostBackParameters(responseString, "aspnetForm");
            string canaryValue = parameters["canary"];
            parameters.Clear();

            parameters["actionVerb"] = "updateCaption";
            parameters["actionValue"] = newDescription;
            parameters["canary"] = canaryValue;

            Uri uriWebFolderItemChangeDescription = GetWebFolderItemChangeDescriptionUri(Session, webFolderItem);
            webBrowser.UploadValuesUrlEncoded(uriWebFolderItemChangeDescription, parameters, false);
        }

        /// <summary>
        /// Deletes a webfolderitem.
        /// </summary>
        /// <param name="webFolderItem">The webfolderitem to be deleted.</param>
        public void DeleteWebFolderItem(WebFolderItemInfo webFolderItem)
        {
            WebBrowser webBrowser = new WebBrowser(Session);
            Uri uriWebFolderItemView = GetWebFolderItemViewUri(Session, webFolderItem);
            string responseString = webBrowser.DownloadString(uriWebFolderItemView);

            NameValueCollection parameters = ParseFormPostBackParameters(responseString, "aspnetForm");
            parameters["postVerb"] = "deleteItem";
            parameters["postVerbData"] = String.Empty;

            webBrowser.UploadValuesUrlEncoded(uriWebFolderItemView, parameters, false);
        }

        #endregion

        #region WebFolder Related Methods

        /// <summary>
        /// Creates a webfolder in SkyDrive's root.
        /// </summary>
        /// <param name="name">The name of the webfolder.</param>
        /// <param name="shareType">The ShareType of the webfolder.</param>
        public void CreateRootWebFolder(string name, WebFolderItemShareType shareType)
        {
            WebBrowser webBrowser = new WebBrowser(Session);
            Uri uriRootWebFolderCreate = GetRootWebFolderCreateUri(Session);
            string responseString = webBrowser.DownloadString(uriRootWebFolderCreate);

            NameValueCollection parameters = ParseFormPostBackParameters(responseString, "aspnetForm");
            parameters["LiveFolderName"] = name;
            // TODO: Implement full featered permissions settings!
            switch (shareType)
            {
                case WebFolderItemShareType.Public:
                case WebFolderItemShareType.Private:
                    parameters["PC_DropDownSelect"] = shareType.ToString().ToLower(CultureInfo.InvariantCulture);
                    break;
                case WebFolderItemShareType.MyNetwork:
                    parameters["PC_DropDownSelect"] = "cn";
                    break;
                case WebFolderItemShareType.PeopleSelected:
                    parameters["PC_DropDownSelect"] = "custom";
                    break;
                default:
                    throw new NotSupportedException(shareType.ToString());
            }

            webBrowser.UploadValuesUrlEncoded(uriRootWebFolderCreate, parameters, false);
        }

        /// <summary>
        /// Creates a webfolder in a sub webfolder.
        /// </summary>
        /// <param name="name">The name of the webfolder.</param>
        /// <param name="webFolderParent">The webfolder where the new webfolder is to be created.</param>
        public void CreateSubWebFolder(string name, WebFolderInfo webFolderParent)
        {
            WebBrowser webBrowser = new WebBrowser(Session);
            Uri uriSubWebFolderCreate = GetSubWebFolderCreateUri(Session, webFolderParent);
            string responseString = webBrowser.DownloadString(uriSubWebFolderCreate);

            NameValueCollection parameters = ParseFormPostBackParameters(responseString, "aspnetForm");
            parameters["itemName"] = name;

            webBrowser.UploadValuesUrlEncoded(uriSubWebFolderCreate, parameters, false);
        }

        /// <summary>
        /// Lists webfolders located in SkyDrive's root.
        /// </summary>
        /// <returns>The list of webfolders in SkyDrive's root.</returns>
        public WebFolderInfo[] ListRootWebFolders()
        {
            List<WebFolderInfo> lRootWebFolder = new List<WebFolderInfo>();

            WebFolderItemInfo[] webFolderItems = ListRootWebFolderItems();
            foreach (WebFolderItemInfo webFolderItem in webFolderItems)
                if (webFolderItem.ItemType == WebFolderItemType.Folder)
                {
                    lRootWebFolder.Add(webFolderItem as WebFolderInfo);
                }

            return lRootWebFolder.ToArray();
        }

        /// <summary>
        /// Lists webfolders located in a sub webfolder.
        /// </summary>
        /// <param name="webFolderParent">The webfolder which webfolders are to be listed.</param>
        /// <returns>The list of webfolders in the sub webfolder.</returns>
        public WebFolderInfo[] ListSubWebFolders(WebFolderInfo webFolderParent)
        {
            List<WebFolderInfo> lSubWebFolder = new List<WebFolderInfo>();

            WebFolderItemInfo[] webFolderItems = ListSubWebFolderItems(webFolderParent);
            foreach (WebFolderItemInfo webFolderItem in webFolderItems)
                if (webFolderItem.ItemType == WebFolderItemType.Folder)
                {
                    lSubWebFolder.Add(webFolderItem as WebFolderInfo);
                }

            return lSubWebFolder.ToArray();
        }

        /// <summary>
        /// Gets a webfolder with all data.
        /// </summary>
        /// <param name="webFolder">The webfolder.</param>
        /// <returns>The webfolder with all data.</returns>
        public WebFolderInfo GetWebFolder(WebFolderInfo webFolder)
        {
            return (WebFolderInfo)GetWebFolderItem(webFolder);
        }

        /// <summary>
        /// Downloads a webfolder's content as a .zip package.
        /// </summary>
        /// <param name="webFolder">The webfolder to be downloaded.</param>
        /// <returns>The webfolder as a .zip package.</returns>
        public Stream DownloadWebFolder(WebFolderInfo webFolder)
        {
            WebBrowser webBrowser = new WebBrowser(Session);
            Uri uriWebFolderItemView = GetWebFolderItemViewUri(Session, webFolder);
            string responseString = webBrowser.DownloadString(uriWebFolderItemView);

            HtmlDocument responseDocument = new HtmlDocument();
            responseDocument.LoadHtml(responseString);

            HtmlNode nodeDownloadLink = responseDocument.GetElementbyId("downloadFolder");
            Uri uriDownload = UriHelper.GetUri(HtmlDocumentHelper.DecodeUnicodeString(
                nodeDownloadLink.Attributes["href"].Value));

            HttpWebRequest webreq = webBrowser.GetHttpWebRequest(uriDownload);
            WebResponse webresp = webreq.GetResponse();
            Stream stream = webresp.GetResponseStream();

            return stream;
        }

        /// <summary>
        /// Renames a webfolder.
        /// </summary>
        /// <param name="webFolder">The webfolder to be renamed.</param>
        /// <param name="newName">The new name.</param>
        public void RenameWebFolder(WebFolderInfo webFolder, string newName)
        {
            RenameWebFolderItem(webFolder, newName);
        }

        /// <summary>
        /// Changes the description of a webfolder.
        /// </summary>
        /// <param name="webFolder">The webfolder to be changed.</param>
        /// <param name="newDescription">The new description.</param>
        public void ChangeWebFolderDescription(WebFolderInfo webFolder, string newDescription)
        {
            ChangeWebFolderItemDescription(webFolder, newDescription);
        }

        /// <summary>
        /// Changes the ContentType of a webfolder in SkyDrive's root.
        /// </summary>
        /// <param name="webFolder">The webfolder.</param>
        /// <param name="newContentType">The new ContenType of the webfolder.</param>
        public void ChangeRootWebFolderContentType(WebFolderInfo webFolder, WebFolderContentType newContentType)
        {
            WebBrowser webBrowser = new WebBrowser(Session);
            Uri uriWebFolderChangeContentType = GetRootWebFolderChangeContentTypeUri(Session, webFolder);
            string responseString = webBrowser.DownloadString(uriWebFolderChangeContentType);

            NameValueCollection parameters = ParseFormPostBackParameters(responseString, "aspnetForm");
            switch (newContentType)
            {
                case WebFolderContentType.Documents:
                    parameters["folderCategory"] = newContentType.ToString().Replace("s", String.Empty);
                    break;
                default:
                    parameters["folderCategory"] = newContentType.ToString();
                    break;
            }

            webBrowser.UploadValuesUrlEncoded(uriWebFolderChangeContentType, parameters, false);
        }

        /// <summary>
        /// Deletes a webfolder.
        /// </summary>
        /// <param name="webFolder">The webfolder to be deleted.</param>
        public void DeleteWebFolder(WebFolderInfo webFolder)
        {
            DeleteWebFolderItem(webFolder);
        }

        #endregion

        #region WebFile Related Methods

        /// <summary>
        /// Uploads a webfile to the specified webfolder.
        /// </summary>
        /// <param name="fileName">The name of the file (including path) to upload.</param>
        /// <param name="webFolderParent">The webfolder where webfile is to be uploaded.</param>
        public void UploadWebFile(string fileName, WebFolderInfo webFolderParent)
        {
            FileInfo fiWebFile = new FileInfo(fileName);
            if (!fiWebFile.Exists)
                throw new FileNotFoundException("WebFile to upload cannot be found!", fiWebFile.FullName);

            WebBrowser webBrowser = new WebBrowser(Session);
            Uri uriUpload = GetWebFileUploadUri(Session, webFolderParent);
            string responseString = webBrowser.DownloadString(uriUpload);

            NameValueCollection parameters = ParseFormPostBackParameters(responseString, "aspnetForm");
            Dictionary<string, object> dicParameter = new Dictionary<string, object>(parameters.Count);
            for (int idxParameter = 0; idxParameter < parameters.Count; idxParameter++)
                dicParameter.Add(
                    parameters.GetKey(idxParameter),
                    parameters[idxParameter]);

            dicParameter["photoSize"] = "0";
            dicParameter["fileUpload1"] = fiWebFile;

            webBrowser.UploadValuesMultipartEncoded(uriUpload, dicParameter, false);
        }

        /// <summary>
        /// Lists webfiles located in a sub webfolder.
        /// </summary>
        /// <param name="webFolderParent">The webfolder which webfiles are to be listed.</param>
        /// <returns>The list of webfiles in the sub webfolder.</returns>
        public WebFileInfo[] ListSubWebFolderFiles(WebFolderInfo webFolderParent)
        {
            List<WebFileInfo> lSubWebFile = new List<WebFileInfo>();

            WebFolderItemInfo[] webFolderItems = ListSubWebFolderItems(webFolderParent);
            foreach (WebFolderItemInfo webFolderItem in webFolderItems)
                if (webFolderItem.ItemType == WebFolderItemType.File)
                {
                    lSubWebFile.Add(webFolderItem as WebFileInfo); 
                }

            return lSubWebFile.ToArray();
        }

        /// <summary>
        /// Gets a webfile with all data.
        /// </summary>
        /// <param name="webFile">The webfile.</param>
        /// <returns>The webfile with all data.</returns>
        public WebFileInfo GetWebFile(WebFileInfo webFile)
        {
            return (WebFileInfo)GetWebFolderItem(webFile);
        }

        /// <summary>
        /// Downloads a webfile.
        /// </summary>
        /// <param name="webFile">The webfile to download.</param>
        /// <returns>The readable stream that contains the webfile's content.</returns>
        public Stream DownloadWebFile(WebFileInfo webFile)
        {
            WebBrowser webBrowser = new WebBrowser(Session);
            Uri uriWebFolderItemView = GetWebFolderItemViewUri(Session, webFile);
            string responseString = webBrowser.DownloadString(uriWebFolderItemView);

            Match matchDownloadUrl = RegexWebFileDownloadUrl.Match(responseString);
            Uri uriDownload = UriHelper.GetUri(HtmlDocumentHelper.DecodeJavascriptString(
                matchDownloadUrl.Groups["URL"].Value));

            HttpWebRequest webreq = webBrowser.GetHttpWebRequest(uriDownload);
            WebResponse webresp = webreq.GetResponse();
            Stream stream = webresp.GetResponseStream();

            return stream;
        }

        /// <summary>
        /// Renames a webfile.
        /// </summary>
        /// <param name="webFile">The webfile to be renamed.</param>
        /// <param name="newName">The new name.</param>
        public void RenameWebFile(WebFileInfo webFile, string newName)
        {
            RenameWebFolderItem(webFile, newName);
        }

        /// <summary>
        /// Changes the description of a webfile.
        /// </summary>
        /// <param name="webFile">The webfile to be changed.</param>
        /// <param name="newDescription">The new description.</param>
        public void ChangeWebFileDescription(WebFileInfo webFile, string newDescription)
        {
            ChangeWebFolderItemDescription(webFile, newDescription);
        }

        /// <summary>
        /// Deletes a webfile.
        /// </summary>
        /// <param name="webFile">The webFile to be deleted.</param>
        public void DeleteWebFile(WebFileInfo webFile)
        {
            DeleteWebFolderItem(webFile);
        }

        #endregion

        #region WebFavorite Related Methods

        /// <summary>
        /// Creates a webfavorite in a sub webfolder.
        /// </summary>
        /// <param name="name">The name of the webfavorite.</param>
        /// <param name="webAddress">The web address of the webfavorite.</param>
        /// <param name="webFolderParent">The webfolder where the webfavorite is to be created.</param>
        public void CreateFavorite(string name, Uri webAddress, WebFolderInfo webFolderParent)
        {
            CreateFavorite(name, webAddress, null, webFolderParent);
        }

        /// <summary>
        /// Creates a webfavorite in a sub webfolder.
        /// </summary>
        /// <param name="name">The name of the webfavorite.</param>
        /// <param name="webAddress">The web address of the webfavorite.</param>
        /// <param name="description">The description of the webfavorite.</param>
        /// <param name="webFolderParent">The webfolder where the webfavorite is to be created.</param>
        public void CreateFavorite(string name, Uri webAddress, string description, WebFolderInfo webFolderParent)
        {
            WebBrowser webBrowser = new WebBrowser(Session);
            Uri uriWebFavoriteCreate = GetWebFavoriteCreateUri(Session, webFolderParent);
            string responseString = webBrowser.DownloadString(uriWebFavoriteCreate);

            NameValueCollection parameters = ParseFormPostBackParameters(responseString, "aspnetForm");
            parameters["itemUrl"] = webAddress != null ? webAddress.AbsoluteUri : String.Empty;
            parameters["itemName"] = name;
            parameters["itemCaption"] = description;

            webBrowser.UploadValuesUrlEncoded(uriWebFavoriteCreate, parameters, false);
        }

        /// <summary>
        /// Gets a webfavorite with all data.
        /// </summary>
        /// <param name="webFavorite">The webfavorite.</param>
        /// <returns>The webfavorite with all data.</returns>
        public WebFavoriteInfo GetWebFavorite(WebFavoriteInfo webFavorite)
        {
            return (WebFavoriteInfo)GetWebFolderItem(webFavorite);
        }

        /// <summary>
        /// Renames a webfavorite.
        /// </summary>
        /// <param name="webFavorite">The webfavorite to be renamed.</param>
        /// <param name="newName">The new name.</param>
        public void RenameWebFavorite(WebFavoriteInfo webFavorite, string newName)
        {
            RenameWebFolderItem(webFavorite, newName);
        }

        /// <summary>
        /// Downloads a webfavorite.
        /// </summary>
        /// <param name="webFavorite">The webfavorite to download.</param>
        /// <returns>The readable stream that contains the webfavorite's content.</returns>
        public Stream DownloadWebFavorite(WebFavoriteInfo webFavorite)
        {
            return DownloadWebFile(webFavorite);
        }

        /// <summary>
        /// Deletes a webfavorite.
        /// </summary>
        /// <param name="webFavorite">The webfavorite to be deleted.</param>
        public void DeleteFavorite(WebFavoriteInfo webFavorite)
        {
            DeleteWebFolderItem(webFavorite);
        }

        #endregion

        #endregion

        #region Private Methods

        #region Uri Methods

        /// <summary>
        /// Gets the URI for creating a webfolder in SkyDrive's root.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <returns>The URI for creating a webfolder in SkyDrive's root.</returns>
        private static Uri GetRootWebFolderCreateUri(WebSession session)
        {
            Uri uriCreate = null;
            if (session != null && !String.IsNullOrEmpty(session.Cid))
                uriCreate = new Uri(String.Format(
                    CultureInfo.InvariantCulture, 
                    "https://{0}.skydrive.live.com/newlivefolder.aspx?ct=skydrive",
                    session.Cid));
            return uriCreate;
        }

        /// <summary>
        /// Gets the URI for creating a webfolder in a sub webfolder.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <param name="webFolderParent">The webfolder where the new webfolder is to be created.</param>
        /// <returns>The URI for creating a webfolder in a sub webfolder.</returns>
        private static Uri GetSubWebFolderCreateUri(WebSession session, WebFolderInfo webFolderParent)
        {
            Uri uriCreate = null;
            if (session != null && !String.IsNullOrEmpty(session.Cid) && webFolderParent != null)
                uriCreate = new Uri(String.Format(
                    CultureInfo.InvariantCulture, 
                    "{0}://{1}.skydrive.live.com/newfolder.aspx{2}?ct=skydrive",
                    webFolderParent.ShareType != WebFolderItemShareType.Public ? "https" : "http",
                    session.Cid,
                    webFolderParent.PathUrl));
            return uriCreate;
        }

        /// <summary>
        /// Gets the URI for changing description of a webfolderitem.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <param name="webFolderItem">The webfolderitem to be changed.</param>
        /// <returns>The URI for changing description of a webfolderitem.</returns>
        private static Uri GetWebFolderItemChangeDescriptionUri(WebSession session, WebFolderItemInfo webFolderItem)
        {
            Uri uriCreate = null;
            if (session != null && !String.IsNullOrEmpty(session.Cid) && webFolderItem != null)
                uriCreate = new Uri(String.Format(
                    CultureInfo.InvariantCulture, 
                    "{0}://{1}.skydrive.live.com/inlineedit.ashx{2}",
                    webFolderItem.ShareType != WebFolderItemShareType.Public ? "https" : "http",
                    session.Cid,
                    webFolderItem.PathUrl));
            return uriCreate;
        }

        /// <summary>
        /// Gets the URI for creating a webfavorite in a sub webfolder.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <param name="webFolderParent">The webfolder where the webfavorite is to be created.</param>
        /// <returns>The URI for creating a webfavorite in a sub webfolder.</returns>
        private static Uri GetWebFavoriteCreateUri(WebSession session, WebFolderInfo webFolderParent)
        {
            Uri uriCreate = null;
            if (session != null && !String.IsNullOrEmpty(session.Cid) && webFolderParent != null)
                uriCreate = new Uri(String.Format(
                    CultureInfo.InvariantCulture, 
                    "{0}://{1}.skydrive.live.com/createfavorite.aspx{2}?ref=1",
                    webFolderParent.ShareType != WebFolderItemShareType.Public ? "https" : "http",
                    session.Cid,
                    webFolderParent.PathUrl));
            return uriCreate;
        }

        /// <summary>
        /// Gets the URI for uploading a webfile to a sub webfolder.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <param name="webFolderParent">The webfolder where webfile is to be uploaded.</param>
        /// <returns>The URI for uploading a webfile to a sub webfolder.</returns>
        private static Uri GetWebFileUploadUri(WebSession session, WebFolderInfo webFolderParent)
        {
            Uri uriUpload = null;
            if (session != null && !String.IsNullOrEmpty(session.Cid) && webFolderParent != null)
                uriUpload = new Uri(String.Format(
                    CultureInfo.InvariantCulture, 
                    "{0}://{1}.skydrive.live.com/upload.aspx{2}?ref=1",
                    webFolderParent.ShareType != WebFolderItemShareType.Public ? "https" : "http",
                    session.Cid,
                    webFolderParent.PathUrl));
            return uriUpload;
        }

        /// <summary>
        /// Gets the URI for browsing a webfolder in SkyDrive's root.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <returns>The URI for browsing a webfolder in SkyDrive's root.</returns>
        private static Uri GetRootWebFolderBrowseUri(WebSession session)
        {
            Uri uriBrowse = null;
            if (session != null && !String.IsNullOrEmpty(session.Cid))
                uriBrowse = new Uri(String.Format(
                    CultureInfo.InvariantCulture, 
                    "http://{0}.skydrive.live.com/home.aspx", 
                    session.Cid));
            return uriBrowse;
        }

        /// <summary>
        /// Gets the URI for browsing a webfolder in a sub webfolder.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <param name="webFolderParent">The webfolder where sub webfolder is to be browsed.</param>
        /// <returns>The URI for browsing a webfolder in a sub webfolder.</returns>
        private static Uri GetSubWebFolderBrowseUri(WebSession session, WebFolderInfo webFolderParent)
        {
            Uri uriBrowse = null;
            if (session != null && !String.IsNullOrEmpty(session.Cid) && webFolderParent != null)
                uriBrowse = new Uri(String.Format(
                    CultureInfo.InvariantCulture, 
                    "{0}://{1}.skydrive.live.com/browse.aspx{2}?view=details",
                    webFolderParent.ShareType != WebFolderItemShareType.Public ? "https" : "http",
                    session.Cid,
                    webFolderParent.PathUrl));
            return uriBrowse;
        }

        /// <summary>
        /// Gets the URI for viewing a webfolderitem.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <param name="webFolderItem">The webfolderitem to view.</param>
        /// <returns>The URI for viewing a webfolderitem.</returns>
        private static Uri GetWebFolderItemViewUri(WebSession session, WebFolderItemInfo webFolderItem)
        {
            Uri uriView = null;
            if (session != null && !String.IsNullOrEmpty(session.Cid) && webFolderItem != null)
                uriView = new Uri(String.Format(
                    CultureInfo.InvariantCulture, 
                    "{0}://{1}.skydrive.live.com/self.aspx{2}",
                    webFolderItem.ShareType != WebFolderItemShareType.Public ? "https" : "http",
                    session.Cid,
                    webFolderItem.PathUrl));
            return uriView;
        }

        /// <summary>
        /// Gets the URI for renaming a webfolderitem.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <param name="webFolderItem">The webfolderitem to rename.</param>
        /// <returns>The URI for renaming a webfolderitem.</returns>
        private static Uri GetWebFolderItemRenameUri(WebSession session, WebFolderItemInfo webFolderItem)
        {
            Uri uriRename = null;
            if (session != null && !String.IsNullOrEmpty(session.Cid) && webFolderItem != null)
                uriRename = new Uri(String.Format(
                    CultureInfo.InvariantCulture, 
                    "{0}://{1}.skydrive.live.com/rename.aspx{2}?ref={3}",
                    webFolderItem.ShareType != WebFolderItemShareType.Public ? "https" : "http",
                    session.Cid,
                    webFolderItem.PathUrl,
                    webFolderItem.ItemType == WebFolderItemType.Folder ? "1" : "2"));
            return uriRename;
        }

        private static Uri GetRootWebFolderChangeContentTypeUri(WebSession session, WebFolderInfo webFolder)
        {
            Uri uriChange = null;
            if (session != null && !String.IsNullOrEmpty(session.Cid) && webFolder != null)
                uriChange = new Uri(String.Format(
                    CultureInfo.InvariantCulture,
                    "{0}://{1}.skydrive.live.com/changefoldertype.aspx{2}",
                    webFolder.ShareType != WebFolderItemShareType.Public ? "https" : "http",
                    session.Cid,
                    webFolder.PathUrl));
            return uriChange;
        }

        #endregion

        #region Form Parsing Methods

        /// <summary>
        /// Parses an HTML form tags which can participate in a postback.
        /// </summary>
        /// <param name="html">The HTML to parse.</param>
        /// <param name="formName">The name of the form to parse.</param>
        /// <returns>The parameters found in the specified form.</returns>
        private static NameValueCollection ParseFormPostBackParameters(string html, string formName)
        {
            HtmlDocument document = new HtmlDocument();
            document.LoadHtml(html);
            return ParseFormPostBackParameters(document, formName);
        }

        /// <summary>
        /// Parses an HTML form tags which can participate in a postback.
        /// </summary>
        /// <param name="document">The HTML document to parse.</param>
        /// <param name="formName">The name of the form to parse.</param>
        /// <returns>The parameters found in the specified form.</returns>
        private static NameValueCollection ParseFormPostBackParameters(HtmlDocument document, string formName)
        {
            HtmlNode nodeForm = document.DocumentNode.SelectSingleNode(
                String.Format(CultureInfo.InvariantCulture, "//form[@name='{0}']", formName));
            HtmlNodeCollection nodeInputs = nodeForm
                .SelectNodes(".//input[@type='text' or @type='hidden' or @type='file'] | .//textarea | .//select");

            NameValueCollection parameters = new NameValueCollection();
            foreach (HtmlNode nodeInput in nodeInputs)
                if (nodeInput.Attributes["name"] != null)
                    parameters.Add(
                        nodeInput.Attributes["name"].Value,
                        nodeInput.Attributes["value"] != null ? nodeInput.Attributes["value"].Value : null);

            return parameters;
        }

        #endregion

        #endregion

    }
}
